package statisticabusiness.engine;

import java.util.*;



/**
 * Implementazione del metodo K-means o di McQueen
 * @author Gagliano Turi, Giuffrida Alberto
 */

public class Kmeans{
    
   int passo, numGruppi, numVar;     
   double coeffMin;
   boolean fine, errore; 
   ArrayList<int[]> dati;
   ArrayList<double[]> centroidi;
   ArrayList<LinkedList<int[]>> gruppi;
   
   
   /**
   * Metodo costruttore<br/>
   * I gruppi vengono creati come un ArrayList di LinkedList, ognuna 
   * contenente le variabili associate a quel gruppo
   * @param dati    i dati immessi dall'utente che verranno inseriti nei gruppi
   * @param numGruppi   il numero di grupi da creare
   * @param numVar      da quante coordinate &egrave; fatta una variabile
   * @param coeffMin    il coefficente di Minkowski
   * @param gruppi      la lista dei gruppi ottenuti
   */
   public Kmeans(ArrayList<int[]> dati, int numGruppi, int numVar, double coeffMin, ArrayList<LinkedList<int[]>> gruppi){
       
        this.numGruppi = numGruppi;
        this.numVar= numVar;
        this.coeffMin=coeffMin;
        passo = 1;
        fine=false;
        errore=false;
        this.dati=dati;
        centroidi=new ArrayList<double[]>();
        this.gruppi=gruppi;
        for(int i=0; i<numGruppi; i++){
            double[] s=new double[numGruppi];
            centroidi.add(s);
        }
    }
    
    /**
     * Avvia il metodo delle K medie
     */
    public void kmeans() {
        while (true) {
                if(errore==true)    break;   
                if      (passo == 1) this.passo1();   //crea i gruppi di partenza 
                else if (passo == 2) this.passo2();     //calcola i centroidi
                else if (passo == 3) this.passo3();     //riassegna i gruppi
                else if ((passo == 4) && (fine==false)) this.passo2();   
                else if ((passo == 4) && (fine==true))  break;      //se stabile si ferma
                else if ((passo == 4) && (errore == true)) break;      //se stabile si ferma
                
	}
       stampa();
       System.out.println("fine k-means");
    }
    
  
    private double calcolaDist(int[] uno, int[] due){
   
        double k=0.0,t=0.0;
        for(int w=0; w<numVar; w++){
                    
            k = Math.abs(uno[w] - due[w]);
            k= Math.pow(k, coeffMin);
            t=t+k;
        }
        return (double)Math.pow(t,(1.0/coeffMin));  //metti 1.0 e non 1 per forzare il risultato a reale e no a intero
    }
   
  
  
    public void passo1(){ 
        double dist1=0.0, dist2=0.0;
        int cont=0; 
        boolean continua=false;
        if(dati.size()<=numGruppi){
            System.out.println("Parametri errati, diminuire il numero di gruppi!");
            errore=true;
            passo=4;
            return;
        }
        //per i primi gruppi prendo i primi elementi qualunque siano
        for(int i=0; i<numGruppi; i++){
            gruppi.add(new LinkedList<int[]>());
            gruppi.get(i).add(dati.get(i));
        }
        //assegno i restanti dati ai centroidi
        for(int i=numGruppi; i<dati.size(); i++){  
            for(int j=0; j<numGruppi; j++){   
                if( Arrays.equals(gruppi.get(j).get(0), dati.get(i))){
                    gruppi.get(j).add(dati.get(i));
                    continua=true;
                    break;
                }
                else{
                    continua=false;
                    dist1 = calcolaDist(gruppi.get(j).get(0), dati.get(i));
                    if(dist2==0.0){
                        dist2=dist1;
                        cont=j;
                    }
                    else if(dist1<dist2){
                        dist2=dist1;
                        cont=j;
                    }
                }
                }
            if(continua) continue;
            gruppi.get(cont).add(dati.get(i));
        }
        passo = 2;
    }
   
  
    public void passo2()
    {   
        double k;
        double[] daCaricare;
        for(int i=0; i<numGruppi; i++){
            daCaricare= new double[numVar];
            for(int j=0; j<numVar; j++){
                k=0.0;
                for (Iterator s = gruppi.get(i).iterator(); s.hasNext();){
                    int[] dato = (int[]) s.next();  
                    k = k + dato[j];
                }
                k = k/((double)(gruppi.get(i).size()));
                daCaricare[j]=k;
            }
            centroidi.set(i, daCaricare);
        }
        
        //stampa i centroidi
        for(int i=0; i<numGruppi; i++){
            System.out.print("Centroide [");
            for(int j=0; j<numVar; j++){
                System.out.print(centroidi.get(i)[j]);
                System.out.print(",");
            }
            System.out.println("]");
        }
        
        passo=3;
    }
    
    private double calcolaDistCentr(int[] uno, double[] due){
   
        double k=0.0,t=0.0;
        for(int w=0; w<numVar; w++){
                    
            k = Math.abs(((double)uno[w]) - due[w]);
            k = Math.pow(k, coeffMin);
            t=t+k;
        }
        return (double)Math.pow(t,(1.0/coeffMin));  //metti 1.0 e non 1 per forzare il risultato a reale e no a intero
    }
    
    public void passo3(){
        
        double dist1,dist2;
        int[] app;
        HashMap<Integer, int[]> daTogliere = new HashMap<Integer, int[]>();
        HashMap<Integer, int[]> daMettere = new HashMap<Integer, int[]>();
        int cont, spostamenti=0;
        
        for(int i=0; i<numGruppi; i++){
           
            Iterator itr = gruppi.get(i).iterator();
            
            while(itr.hasNext()){
                app  = (int[]) itr.next();
                cont=0;
                dist1 = calcolaDistCentr( app , centroidi.get(cont));
                
                for(int j=1; j<numGruppi; j++){
                    dist2 = calcolaDistCentr( app , centroidi.get(j));
                    if(dist1>=dist2){
                        dist1=dist2;
                        cont=j;
                    }
                }
                if(cont!=i){
                    daTogliere.put(i, app);
                    daMettere.put(cont, app);
                }
            }
        }   
        Integer key;
        Iterator mit = daTogliere.keySet().iterator(); 
        while(mit.hasNext()){
            key = (Integer) mit.next();
            app = (int[]) daTogliere.get(key); 
            gruppi.get(key).remove(app);
        }
        Iterator mit2 = daMettere.keySet().iterator();   
        while(mit2.hasNext()){
            key = (Integer) mit2.next();
            app = (int[]) daMettere.get(key); 
            gruppi.get(key).add(app);
            spostamenti++;
        }
        if(spostamenti==0) fine=true;
        passo=4;
    }
    
    /**
     * Stampa gli elementi di ogni gruppo ottenuto  nella console. <br/>
     * Utile in fase di debug
     */
    public void stampa(){
        
        int[] app;
        for(int i=0; i<numGruppi; i++){
            System.out.println("Nel gruppo: " + i+ "sono presenti");
            Iterator it= gruppi.get(i).iterator();
            while(it.hasNext()){
            
                app= (int[]) it.next();
                System.out.print("[");
                
                if(app==null) continue;
                
                for(int w=0; w<app.length ;w++){
                    System.out.print(app[w]+ ",");
                }
                System.out.println("]");
            }
        }
        System.out.println("fine stampa");
    }
    
}
